/*
 * Copyright (c) 2018-2023 北京华恒盛世科技有限公司
 * QUICKPOOL is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *     http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
*/

#include "xdrbase.h"
#include "channel.h"
#include "api.h"

static int save_file(int , char **, int, char *);

/**
 * @brief       API: call EE to copy file from one node to another.
 *
 * @param[in]   option       #1: sub > exec, exec > sub, sub >> exec, exec >> sub
 * @param[in]   hostname     #2: node name to be called
 * @param[in]   username     #3: who do the file copy
 * @param[in]   fromfile     #4: from file name
 * @param[in]   tofile       #5: to file name
 * @param[in]   master       #6: master(port and name) to be called
 *
 * @retval      0            #1: succeed
 * @retval      -1           #2: failed
 */
int 
client_copy_file(int option, char *hostname, char *username,
                 char *fromfile, char *tofile, struct poolMaster *master)
{
    int num = 0;
    /*set params*/
    num += 4; //option user leftfile rightfile  

    if (option & XF_OP_EXEC2SUB || option & XF_OP_EXEC2SUB_APPEND) {
        num++;
    }
    
    struct request fileRequest;
    memset(&fileRequest, 0, sizeof(struct request));

    fileRequest.number = num;
    fileRequest.keyvalues = (struct keyvalue *)calloc(fileRequest.number, sizeof(struct keyvalue));
    if (NULL == fileRequest.keyvalues) {
        quickpool_errno = ERROR_MEMORY;
        log_message(ERR, NOCLASS, STR_FUNC_D_FAIL_M, __func__, "calloc",
                    fileRequest.number*sizeof(struct keyvalue));
        return -1;
    }

    num = 0;
    fileRequest.keyvalues[num].key = REQUEST_KEY_FILECOPY_MODE;
    fileRequest.keyvalues[num].value = (char *)calloc(12, sizeof(char));
    if (NULL == fileRequest.keyvalues[num].value) {
        quickpool_errno = ERROR_MEMORY;
        log_message(ERR, NOCLASS, STR_FUNC_D_FAIL_M, __func__, "calloc", 12);
        return -1;
    }
    snprintf(fileRequest.keyvalues[num].value, 12, "%d", option);
    num ++;

    fileRequest.keyvalues[num].key = REQUEST_KEY_FILECOPY_UESR;
    fileRequest.keyvalues[num].value = copy_string(username);
    num++;
    
    fileRequest.keyvalues[num].key = REQUEST_KEY_FILECOPY_LFILE;
    fileRequest.keyvalues[num].value = copy_string(fromfile);
    num++;

    fileRequest.keyvalues[num].key = REQUEST_KEY_FILECOPY_RFILE;
    fileRequest.keyvalues[num].value = copy_string(tofile);
    num++;
    
    int numbuf = 0;
    if (option & XF_OP_EXEC2SUB || option & XF_OP_EXEC2SUB_APPEND) {

        struct stat fileinfo;
        stat(fromfile, &fileinfo);
        if (0 == fileinfo.st_size) {
            log_message(ERR, NOCLASS, "%s: file <%s> has no contetn to send.", __func__, fromfile);
            return -1;
        }
        numbuf = fileinfo.st_size / MAXMESSAGELENGTH;
        if (fileinfo.st_size % MAXMESSAGELENGTH) {
            numbuf += 1;
        }

        //default the buf size is MAXMESSAGELENGTH
        fileRequest.keyvalues[num].key = REQUEST_KEY_FILECOPY_NUMBUF;
        fileRequest.keyvalues[num].value = (char *)calloc(12, sizeof(char));
        if (NULL == fileRequest.keyvalues[num].value) {
            quickpool_errno = ERROR_MEMORY;
            log_message(ERR, NOCLASS, STR_FUNC_D_FAIL_M, __func__, "calloc", 12);
            return -1;
        }
        snprintf(fileRequest.keyvalues[num].value, 12, "%d", numbuf);
        num ++;            
    }
    
    struct auth auth;
    gf_set_auth(&auth, commandParams[AUTH_KIND].string_value);

    unsigned int size; // for calculating the size of the request.
    size = gf_xdrsize_request(&fileRequest);
    size += gf_xdrsize_auth(&auth);
    size += HEADER_LEN;
    
    char *request;
    request = (char *)calloc(size, sizeof(char));
    if (NULL == request) {

        log_message(ERR, NOCLASS, STR_FUNC_D_FAIL_M, __func__, "calloc", size);
        gf_xdr_free(gf_xdr_auth, (void *)&auth);
        gf_xdr_free(gf_xdr_request, &fileRequest);
        quickpool_errno = ERROR_MEMORY;

        return -1;
    }

    XDR xdrs;
    xdrmem_create(&xdrs, request, size, XDR_ENCODE);

    struct header tohdr, outhdr;
    gf_init_header(&tohdr);
    tohdr.reqcode = BATCH_FILE_COPY;

    if (!gf_xdr_message(&xdrs, &fileRequest , &tohdr, &auth, gf_xdr_request, NULL)) {

        // fail to encode request.
        log_message(ERR, NOCLASS, STR_FUNC_FAIL, __func__, "gf_xdr_message(request)");
        xdr_destroy(&xdrs);
        FREEUP(request);
        gf_xdr_free(gf_xdr_auth, (void *)&auth);
        gf_xdr_free(gf_xdr_request, &fileRequest);
        quickpool_errno = ERROR_XDR;

        return -1;
    }

    char *host;
    unsigned int port;
    unsigned int connTimeout;
    unsigned int recvTimeout;
    if (NULL != master) { // client set master, let's call it directly.
        host = master->master;
        port = master->port;
        connTimeout = master->conntimeout;
        recvTimeout = master->recvtimeout;
    } else {
        host = hostname;
        port = baseParams[BASE_EE_PORT].int_value;
        connTimeout = g_conntimeout;
        recvTimeout = g_recvtimeout;
    }

    char *reply;
    int cc, fd = -1;
    cc = gf_callslave(host, port, connTimeout, recvTimeout,
                      XDR_GETPOS(&xdrs), request, &outhdr, &reply, &fd);

    xdr_destroy(&xdrs);
    FREEUP(request);
    gf_xdr_free(gf_xdr_auth, (void *)&auth);
    gf_xdr_free(gf_xdr_request, &fileRequest);

    if (0 > cc) { // communication in trouble.

        if (0 < fd) {
            gf_chan_close(fd);
        }

        FREEUP(reply);
        log_message(ERR, NOCLASS, STR_FUNC_S_FAIL, __func__, "gf_callmaster", host);

        return -1;
    }

    if (0 < numbuf) { // send file buf to remote host

        quickpool_errno = outhdr.reqcode;
        xdrmem_create(&xdrs, reply, outhdr.length, XDR_DECODE);

        // if ok to send file buf.
        char *connectbk=NULL;
        if (!gf_xdr_string(&xdrs, &connectbk, &outhdr, NULL)) {

            log_message(ERR, NOCLASS, STR_FUNC_FAIL, __func__, "gf_xdr_string(ok)");
            xdr_destroy(&xdrs);
            FREEUP(reply);
            gf_chan_close(fd);
            return -1;
        }

        xdr_destroy(&xdrs);
        FREEUP(reply); 

        if (0 != strcmp(connectbk, "ok")) {

            log_message(ERR, NOCLASS, STR_FUNC_FAIL, __func__, "not ok");
            gf_chan_close(fd);
            return -1;
        }
        
        FILE *fp = NULL;
        int size = 0, ret;
        char data[MAXMESSAGELENGTH];
        char message[1024];
        memset(message, 0, sizeof(message));

        if ((fp = fopen(fromfile, "r")) == NULL) {

            log_message(ERR, NOCLASS, STR_FUNC_S_FAIL_M, __func__, "fopen", fromfile);
            gf_chan_close(fd);
            return -1;
        }

        do {
            size = fread(data, 1, MAXMESSAGELENGTH, fp);
            if (0 < size) {
                ret = gf_chan_write(fd, data, size, 5);
                if (ret < 0) {

                    log_message(ERR, NOCLASS, STR_FUNC_FAIL, __func__, "gf_chan_write");
                    gf_chan_close(fd);
                    return -1;
                }
            }
            
        } while (size == MAXMESSAGELENGTH);
    } else { // receive file buf from remote host.

        quickpool_errno = outhdr.reqcode;

        struct QuickPoolFileCopyReply *file;
        file = (struct QuickPoolFileCopyReply *)calloc(1, sizeof(struct QuickPoolFileCopyReply));
        if (NULL == file) {

            log_message(ERR, NOCLASS, STR_FUNC_D_FAIL_M, __func__,
                        "calloc", sizeof(struct QuickPoolFileCopyReply));
            FREEUP(reply);
            gf_chan_close(fd);
            return -1;
        }

        xdrmem_create(&xdrs, reply, outhdr.length, XDR_DECODE);
        if (!libxdr_filecopy(&xdrs, file, &outhdr, NULL)) {

            log_message(ERR, NOCLASS, STR_FUNC_FAIL, __func__, "libxdr_filecopy");
            xdr_destroy(&xdrs);
            FREEUP(reply);
            gf_chan_close(fd);
            return -1;
        }

        xdr_destroy(&xdrs);
        FREEUP(reply);

        // save the file.
        if (0 > save_file(option, file->file_buffer, file->num_buf, tofile)) {

            log_message(ERR, NOCLASS, STR_FUNC_S_FAIL, __func__, "copy_file", tofile);
            gf_chan_close(fd);
            return -1;
        }           
    }

    gf_chan_close(fd);

    return 0;
} // end file client_copy_file

/**
 * @brief       save the data to file.
 *
 * @param[in]   option       #1: sub > exec, exec > sub, sub >> exec, exec >> sub
 * @param[in]   data         #2: data list to be saved
 * @param[in]   num          #3: number of data
 * @param[in]   file         #4: file name to save the data
 *
 * @retval      0            #1: succeed
 * @retval      -1           #2: failed
 */
static int 
save_file(int option, char **data, int num, char *file)
{
    if (NULL == file || NULL == data || 0 > num) {
        return -1;
    }

    FILE *fp = NULL;
    if ((option == XF_OP_EXEC2SUB_APPEND) || (option == XF_OP_SUB2EXEC_APPEND)) { // append

        fp = fopen(file, "a+");
        if (NULL == fp) {
            log_message(ERR, NOCLASS, STR_FUNC_S_FAIL_M, __func__, "fopen", file);
            return -1;
        }

        int i, ret;
        for (i = 0; i < num; i++) {

            ret = fwrite(data[i], strlen(data[i]), 1, fp);
            if (0 > ret) {
                log_message(ERR, NOCLASS, STR_FUNC_S_FAIL_M, __func__, "fwrite", file);
                fclose(fp);
                return -1;
            }
        }

        fclose(fp);

        return 0;
    }

    if ((option == XF_OP_EXEC2SUB) || (option == XF_OP_SUB2EXEC)) {

        struct stat fileinfo;
        if (0 == stat(file, &fileinfo)) {

            //if the file has exist, rename the file to file.old.timestamp
            char newFileName[PATH_MAX+1];
            snprintf(newFileName, PATH_MAX, "%s.old.%ld", file, time(NULL));
            if (rename(file, newFileName) < 0) {
                log_message(ERR, NOCLASS, STR_FUNC_S_FAIL_M, __func__, "rename", file);
                return -1;
            }
        }

        fp = fopen(file, "a");
        if (NULL == fp) {
            log_message(ERR, NOCLASS, STR_FUNC_S_FAIL_M, __func__, "fopen", file);
            return -1;
        }

        int i, ret;
        for (i = 0; i < num; i++) {

            ret = fwrite(data[i], strlen(data[i]), 1, fp);
            if (0 > ret) {
                log_message(ERR, NOCLASS, STR_FUNC_S_FAIL_M, __func__, "fwrite", file);
                fclose(fp);
                return -1;
            }
        }        

        fclose(fp);
    }   

    return 0;
} // end function save_file
